Composizione al posto dell'ereditarietà

Da Wikipedia, l'enciclopedia libera.
Vai alla navigazione Vai alla ricerca
Il diagramma mostra come il comportamento volo e suono di un animale può essere ideato in un modo flessibile attraverso l'uso del principio di progettazione della composizione al posto dell'ereditarietà.[1]

La composizione al posto dell'ereditarietà (o principio del riutilizzo dei composti) nella programmazione orientata agli oggetti è il principio secondo cui le classi dovrebbero implementare il comportamento polimorfico e il riutilizzo del codice mediante la composizione (ovvero contenendo istanze di classi che implementano le funzionalità desiderate) invece che mediante l'ereditarietà da classi base.[2] Spesso questo è un principio dichiarato di OOP, come ad esempio nell'influente Design Patterns: "preferire la composizione di oggetti rispetto all'ereditarietà delle classi."[3]

Alcuni linguaggi, specialmente Go, usano esclusivamente questo tipo di composizione.

Le basi[modifica | modifica wikitesto]

Un'implementazione di composizione al posto dell'ereditarietà di solito inizia con la creazione di diverse interfacce che rappresentano i comportamenti che il sistema deve esibire. L'uso di interfacce permette a questa tecnica di supportare il comportamento polimorfico che è così importante nella programmazione orientata agli oggetti. Le classi che implementano le interfacce identificate sono sviluppate e aggiunte alle classi del dominio di applicazione secondo necessità. Così, i comportamenti del sistema sono realizzati senza l'ereditarietà. In realtà, le classi del dominio di applicazione possono essere tutte classi base, senza alcuna ereditarietà. L'implementazione alternativa dei comportamenti del sistema si realizza fornendo un'altra classe che implementa l'interfaccia col comportamento desiderato. Ogni classe del dominio di applicazione che contiene un riferimento all'interfaccia può supportare facilmente qualsiasi implementazione di tale interfaccia e la scelta può anche essere ritardata fino al tempo di esecuzione.

Ereditarietà[modifica | modifica wikitesto]

Quello che segue è un esempio in C++11:

class GameObject {
    public:
        virtual ~GameObject() {}
        virtual void update() {}
        virtual void draw() {}
        virtual void collide(GameObject objects[]) {}
};

class Visible : public GameObject {
    public:
        void draw() override { /* draw model at position of this object */ };
    private:
        Model* model;
};

class Solid : public GameObject {
    public:
        void collide(GameObject objects[]) override { /* check and react to collisions with objects */ };
};

class Movable : public GameObject {
    public:
        void update() override { /* update position */ };
};

Vantaggi[modifica | modifica wikitesto]

Favorire la composizione al posto dell'ereditarietà è un principio di progettazione che offre al progettista maggiore flessibilità, realizzando nel lungo termine le classi del dominio di applicazione e un dominio di applicazione più stabile. In altre parole, una relazione del tipo "HA-UN" può essere migliore di una del tipo "È-UN".[1]

La progettazione iniziale è semplificata identificando i comportamenti del sistema nelle interfacce separate invece di creare una relazione gerarchica per distribuire i comportamenti tra le classi del dominio di applicazione con l'ereditarietà. Questo approccio può ospitare più facilmente futuri cambiamenti dei requisiti che altrimenti richiederebbero una ristrutturazione completa delle classi del dominio di applicazione nel modello di ereditarietà. Inoltre, evita problemi spesso associati con modifiche relativamente minori ad un modello basato dell'ereditarietà che comprende diverse generazioni di classi.

Svantaggi[modifica | modifica wikitesto]

Uno svantaggio nell'uso della composizione al posto dell'ereditarietà è che tutti i metodi essendo forniti dalle classi composte devono essere implementati nella classe derivata, anche se ci sono solo metodi di inoltro. Al contrario, l'ereditarietà non richiede che tutti i metodi di una classe base devono essere ri-implementati all'interno della classe derivata. Piuttosto, la classe derivata necessita solo di implementare (override) i metodi che hanno un comportamento differente rispetto ai metodi della classe base. Questo può richiedere significativamente meno sforzo di programmazione se la classe base contiene molti metodi che forniscono un comportamento predefinito e solo pochi di loro necessitano di essere sovrascritti all'interno della classe derivata.

Questo inconveniente può essere evitato utilizzando trait o mixin. Alcuni linguaggi, come Perl 6, forniscono una parola chiave di gestione per facilitare l'inoltro dei metodi. In Java, il progetto Lombok consente di implementare la delega utilizzando un'unica annotazione @Delegate Archiviato il 14 aprile 2015 in Internet Archive. sul campo invece di copiare e mantenere i nomi ed i tipi di tutti i metodi dal campo delegato.

Note[modifica | modifica wikitesto]

  1. ^ a b Eric Freeman, Elisabeth Freeman, Kathy Sierra e Bert Bates, Head First Design Patterns (paperback), a cura di Hendrickson e Mike Loukides, vol. 1, O'Reilly, 2004, p. 23, ISBN 978-0-596-00712-6.
  2. ^ Kirk Knoernschild, Java Design - Objects, UML, and Process: 1.1.5 Composite Reuse Principle (CRP), Addison-Wesley Inc., 2002. URL consultato il 23 maggio 2015.
  3. ^ Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, Design Patterns, 1994, p. 20.